﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Mono.Cecil;

namespace EzDeploy.Core
{
    public class AssemblyReferenceResolver : IAssemblyReferenceResolver
    {
        readonly Dictionary<string, AssemblyDefinitionComposite> _assemblies = new Dictionary<string, AssemblyDefinitionComposite>();

        public void Reset()
        {
            if (_assemblies != null)
                _assemblies.Clear();
        }

        public List<AssemblyInfo> Resolve(Project project, IAssemblyResolver resolver)
        {
            string assemblyFile = project.AssemblyFile;

            var assemblies = new List<AssemblyInfo>();
            if (File.Exists(assemblyFile))
            {
                var assembly = AssemblyDefinition.ReadAssembly(assemblyFile, new ReaderParameters(ReadingMode.Deferred));
                project.RuntimeVersion = CecilUtility.ToFriendlyName(assembly.MainModule.Runtime);
                project.AssemblyHasPublicKey = assembly.MainModule.Assembly.Name.HasPublicKey;

                _assemblies.Add(assembly.MainModule.Assembly.Name.FullName, new AssemblyDefinitionComposite(assembly, assembly.MainModule.Assembly.Name, VersionStatus.Match));

                this.ResolveRecursive(assembly, resolver);
                _assemblies.Remove(assembly.FullName);
            }
            else
            {
                project.RuntimeVersion = "Unknown";
                project.AssemblyHasPublicKey = false;
            }

            foreach (var assembly in _assemblies)
                assemblies.Add(new AssemblyInfo(assembly.Value));

            this.Reset();

            return assemblies;
        }

        private void ResolveRecursive(AssemblyDefinition assembly, IAssemblyResolver resolver)
        {
            foreach (var reference in assembly.MainModule.AssemblyReferences)
            {
                if (!_assemblies.ContainsKey(reference.FullName))
                {
                    try
                    {
                        var referenceAssembly = resolver.Resolve(reference, new ReaderParameters(ReadingMode.Deferred));
                        if (!_assemblies.ContainsKey(referenceAssembly.MainModule.Assembly.FullName))
                        {
                            VersionStatus fs = String.Equals(reference.FullName, referenceAssembly.FullName, StringComparison.InvariantCulture)? VersionStatus.Match : VersionStatus.Mismatch;
                            var composite = new AssemblyDefinitionComposite(referenceAssembly, referenceAssembly.MainModule.Assembly.Name, fs);
                            composite.Attributes["AssemblyCompany"] = this.GetCompanyNameAttributeValue(referenceAssembly);

                            _assemblies.Add(referenceAssembly.MainModule.Assembly.FullName, composite);

                            this.ResolveRecursive(referenceAssembly, resolver);
                        }
                    }
                    catch (AssemblyResolutionException)
                    {
                        _assemblies.Add(reference.FullName, new AssemblyDefinitionComposite(null, reference, VersionStatus.Unknown));
                    }
                }
            }
        }

        private string GetCompanyNameAttributeValue(AssemblyDefinition assembly)
        {
            foreach (var att in assembly.MainModule.Assembly.CustomAttributes)
            {
                string attributeFullName = att.AttributeType.FullName;
                if (String.Equals(attributeFullName, typeof(AssemblyCompanyAttribute).FullName, StringComparison.CurrentCulture))
                {
                    var companyName = att.ConstructorArguments[0].Value as string;
                    return companyName;
                }
            }

            return string.Empty;
        }
    }
}